package com.have.blog.admin.service.impl;


import com.have.blog.admin.domain.bo.AdminUserDetail;
import com.have.blog.admin.constant.RedisExpiredTime;
import com.have.blog.admin.redis.AdminRoleCacheService;
import com.have.blog.admin.utils.SecurityUtil;
import com.have.blog.base.enums.BlogStatus;
import com.have.blog.security.bo.OnlineAdmin;
import com.have.blog.admin.constant.AdminBlogConstant;
import com.have.blog.admin.domain.dto.AdminInfoVo;
import com.have.blog.admin.redis.AdminLoginCacheService;
import com.have.blog.admin.service.AdminLoginService;
import com.have.blog.base.constant.BaseBlogConstant;
import com.have.blog.base.constant.BaseConstant;
import com.have.blog.base.enums.AdminMenuEnum;
import com.have.blog.base.wrapper.ResultCode;
import com.have.blog.base.wrapper.Wrapper;
import com.have.blog.config.configs.jwt.Audience;
import com.have.blog.config.hloder.RequestHolder;
import com.have.blog.model.mapper.AdminMapper;
import com.have.blog.model.mapper.AdminRoleMapper;
import com.have.blog.model.mapper.ResourceMenuMapper;
import com.have.blog.model.mapper.RoleResourceMenuRelationMapper;
import com.have.blog.model.models.*;
import com.have.blog.picture.service.PictureFeignApi;
import com.have.blog.security.uitl.JwtTokenUtil;
import com.have.blog.utils.IpUtils;
import com.have.blog.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@SuppressWarnings("all")
public class AdminLoginServiceImpl  implements AdminLoginService{
    @Autowired
    private AdminLoginCacheService adminCacheService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private Audience audience;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RoleResourceMenuRelationMapper roleResourceMenuRelationMapper;
    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private AdminRoleMapper roleMapper;
    @Autowired
    private ResourceMenuMapper resourceMenuMapper;
    @Autowired
    private PictureFeignApi pictureFeignApi;

    @Value("${redis.admin.isRememberMeExpiredSeconds}")
    private long isRemeberMeExpiredSeconds;
    @Value("${jwt.tokenHead}")
    private String tokenHead;


    /**
     * 新增改版
     */
    @Autowired
    private AdminRoleCacheService adminRoleCacheService;

    /**
     * 登录服务
     * @param request
     * @param username
     * @param password
     * @param isRememberMe
     * @return
     */
    @Override
    public Wrapper login(HttpServletRequest request, String username, String password, Boolean isRememberMe) {
        // 1. 根据Ip地址到redis中查询用户登录错误次数，如果大于阈值则拒绝访问。
        String ipAddr = IpUtils.getIpAddr(request);
        String loginErrorCount = adminCacheService.getLoginErrorCount(ipAddr);
        if (!StringUtils.isEmpty(loginErrorCount)) {
            if (Integer.valueOf(loginErrorCount) >= 10) {
                return Wrapper.failed(ResultCode.LOGIN_COUNT_ERROR.getCode(), ResultCode.LOGIN_COUNT_ERROR.getMessage());
            }
        }
        // 3. 查询用户
        Admin admin = loadAdminByUsername(username);
        if (admin == null) {
            return Wrapper.failed(ResultCode.LOGIN_ERROR.getCode(), String.format(ResultCode.LOGIN_ERROR.getMessage(), setLoginCommit(request)));
        }
        UserDetails userDetails = loadUserDetailByAdmin(admin);
        if (!passwordEncoder.matches(password, userDetails.getPassword())) { // 密码错误, 返回错误信息，更新缓存
            return Wrapper.failed(ResultCode.LOGIN_ERROR.getCode(), String.format(ResultCode.LOGIN_ERROR.getMessage(), setLoginCommit(request)));
        }
        // 将登录用户交付给springsecurity上下文管理
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        String roleName = ((AdminUserDetail) userDetails).getRoleName();
        // 4. 根据是否记住我设置token过期时间, token相关
        RedisExpiredTime expiredTime = isRememberMe ? RedisExpiredTime.ONE_HOUR : RedisExpiredTime.SEVEN_DAY;
        String jwtToken = jwtTokenUtil.generateJwtToken(userDetails.getUsername(),
                roleName,
                audience.getClientId(),
                audience.getIssuer(),
                expiredTime.getTimeUnit().toMillis(expiredTime.getDuration()),
                audience.getBase64Secret());

        Map<String, Object> resultMap = new HashMap<>(BaseConstant.NUM_ONE);
        resultMap.put(BaseBlogConstant.TOKEN, jwtToken);
        // 5. 更新数据库中相关用户信息
        updateAdmin(admin, ipAddr);
        // 6. 添加用户到缓存
        adminCacheService.addOnlineAdmin(admin, jwtToken, StringUtils.getUUID(), roleName, expiredTime.getDuration(), expiredTime.getTimeUnit());
        return Wrapper.success(resultMap);
    }

    /**
     * 获取用户信息
     * @return
     */
    @Override
    public Wrapper info() {
        AdminUserDetail principal = (AdminUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String username = principal.getUsername();
        String roleName = principal.getRoleName();
        Admin admin = loadAdminByUsername(username);
        AdminRole role = loadRoleByUid(admin.getRoleUid());
        AdminInfoVo adminInfoVo = new AdminInfoVo();
        adminInfoVo.setRoleName(roleName);
        if (admin.getHeadUid() != null) {
            Wrapper<String> pictureWrapper = pictureFeignApi.getPictureUrl(admin.getHeadUid());
            if (pictureWrapper.isSuccess()) {
                adminInfoVo.setAvatarUrl(pictureWrapper.getData());
            }
        }
        return Wrapper.success(adminInfoVo);
    }

    @Override
    public AdminRole loadRoleByUid(Integer roleUid) {
        AdminRole adminRole = roleMapper.selectByPrimaryKey(roleUid);
        return adminRole;
    }

    @Override
    public Admin loadAdminByUsername(String username) {
        AdminExample adminExample = new AdminExample();
        adminExample.createCriteria()
                .andUsernameEqualTo(username)
                .andStatusEqualTo(BlogStatus.ENABLE.getStatus());
        List<Admin> admins = adminMapper.selectByExample(adminExample);
        if (CollectionUtils.isEmpty(admins)) {
            return null;
        }
        return admins.get(0);
    }

    @Override
    public UserDetails loadUserDetailByAdmin(Admin admin) {
        AdminRole adminRole = loadRoleByUid(admin.getRoleUid());
        List<ResourceMenu> resourceMenus = loadResourceMenuByRoleUid(adminRole.getUid());
        if (CollectionUtils.isEmpty(resourceMenus)) {
            resourceMenus = new ArrayList<>();
        }
        return new AdminUserDetail(admin, adminRole.getRoleName(), resourceMenus);
    }

    /**
     * 退出登录
     * @return
     */
    @Override
    public Wrapper logout() {
        String token = RequestHolder.getToken();
        if (StringUtils.isEmpty(token)) {
            return Wrapper.unauthorized();
        }
        // 删除缓存中的用户信息
        OnlineAdmin onlineAdmin = adminCacheService.getOnlineAdmin(token);
        if (onlineAdmin != null) {
            String tokenUId = onlineAdmin.getTokenUId();
            adminCacheService.deleteToken(tokenUId);
            adminCacheService.deleteOnlineAdmin(token);
        }
        return Wrapper.success();
    }

    @Override
    public Wrapper getMenu() {
        String roleName = SecurityUtil.getRoleName();
        Integer roleId = adminRoleCacheService.getRoleId(roleName);
        if (roleId == null) {
            AdminUserDetail principal = (AdminUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String username = principal.getUsername();
            Admin admin = loadAdminByUsername(username);
            roleId = admin.getRoleUid();
        }
        List<ResourceMenu> resourceMenuList = loadResourceMenuByRoleUid(roleId);

        List<ResourceMenu> firstList = resourceMenuList.stream().filter(item -> {
            return item.getMenuType() == AdminMenuEnum.MENU.getType() && item.getMenuLevel() == AdminBlogConstant.ONE;
        }).collect(Collectors.toList());
        List<ResourceMenu> secondList = resourceMenuList.stream().filter(item -> {
            return item.getMenuType() == AdminMenuEnum.MENU.getType() && item.getMenuLevel() == AdminBlogConstant.TWO;
        }).collect(Collectors.toList());
        List<ResourceMenu> thirdsList = resourceMenuList.stream().filter(item -> {
            return item.getMenuType() == AdminMenuEnum.BUTTON.getType() && item.getMenuLevel() == AdminBlogConstant.THREE;
        }).collect(Collectors.toList());
        Map<String, List<ResourceMenu>> map = new HashMap();
        map.put(AdminBlogConstant.PARENT_LIST, firstList);
        map.put(AdminBlogConstant.SON_LIST, secondList);
        map.put(AdminBlogConstant.BUTTON_LIST, thirdsList);
        return Wrapper.success(map);
    }

    @Override
    public List<ResourceMenu> loadResourceMenuByRoleUid(Integer roleUid) {
        List<ResourceMenu> roleResource = adminRoleCacheService.getRoleResource(roleUid);
        if (!CollectionUtils.isEmpty(roleResource)) {
            return roleResource;
        }
        RoleResourceMenuRelationExample example = new RoleResourceMenuRelationExample();
        example.createCriteria()
                .andRoleUidEqualTo(roleUid);
        List<RoleResourceMenuRelation> roleResourceRelations = roleResourceMenuRelationMapper.selectByExample(example);
        List<Integer> resourceMenuUids = new ArrayList<>();
        roleResourceRelations.forEach(item -> {
            resourceMenuUids.add(item.getResourceMenuUid());
        });
        ResourceMenuExample resourceMenuExample = new ResourceMenuExample();
        resourceMenuExample.createCriteria()
                .andUidIn(resourceMenuUids)
                .andStatusEqualTo(BlogStatus.ENABLE.getStatus());
        List<ResourceMenu> resourceMenus = resourceMenuMapper.selectByExample(resourceMenuExample);
        adminRoleCacheService.setRoleResource(roleUid, resourceMenus, RedisExpiredTime.ONE_DAY.getDuration(), RedisExpiredTime.ONE_DAY.getTimeUnit());
        return resourceMenus;
    }

    /**
     * 更新用户信息
     * @param admin
     * @param ip
     */
    private void updateAdmin(Admin admin, String ip) {
        Integer count = admin.getLoginCount();
        admin.setLoginCount(count + 1);
        admin.setLastLoginIp(ip);
        admin.setLastLoginTime(new Date());
        adminMapper.updateByPrimaryKeySelective(admin);
    }

    /**
     * 设置登录限制,返回剩余次数
     */
    private Integer setLoginCommit(HttpServletRequest request) {
        String ipAddr = IpUtils.getIpAddr(request);
        String loginErrorCount = adminCacheService.getLoginErrorCount(ipAddr);
        Integer surplusCount = BaseConstant.NUM_5000;
        if (!StringUtils.isEmpty(loginErrorCount)) {
            Integer tempCount = Integer.valueOf(loginErrorCount) + 1;
            surplusCount -= tempCount;
            adminCacheService.setLoginErrorCount(ipAddr, String.valueOf(tempCount), 10, TimeUnit.MINUTES);
        } else {
            surplusCount -= 1;
            adminCacheService.setLoginErrorCount(ipAddr, BaseConstant.STR_ONE, 30, TimeUnit.MINUTES);
        }
        return surplusCount;
    }

}
